/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tencent.tendon.convert.json;

import com.tencent.tendon.convert.util.CharArray;

/**
 *
 * @author nbzhang
 */
public final class JsonDefaultReader extends JsonReader {

    private char[] text;

    private int limit;

    JsonDefaultReader() {
    }

    public JsonDefaultReader(String json) {
        this.text = json.toCharArray();
    }

    public JsonDefaultReader(char[] text) {
        this.text = text;
    }

    public JsonDefaultReader(char[] text, int start, int len) {
        this.text = text;
        this.position = start - 1;
        this.limit = start + len - 1;
    }

    public final void text(String text) {
        text(text.toCharArray());
    }

    public final void text(char[] text) {
        this.text = text;
        this.position = -1;
        this.limit = text.length - 1;
    }

    public final void text(char[] text, int start, int len) {
        this.text = text;
        this.position = start - 1;
        this.limit = start + len - 1;
    }

    @Override
    public void prepare() {
    }

    @Override
    public void release() {
        super.release();
        this.limit = -1;
        this.text = null;
    }

    @Override
    public void close() {
        JsonPool.offer(this);
    }

    public final void position(int position) {
        this.position = position;
    }

    @Override
    protected char nextChar() {
        return this.text[++this.position];
    }

    @Override
    public void checkObject() {
        char ch = this.text[++this.position];
        if (ch == '{') return;
        if (ch <= ' ') {
            for (;;) {
                ch = this.text[++this.position];
                if (ch > ' ') break;
            }
            if (ch == '{') return;
        }
        throw new JsonException("a json object text must begin with '{' (position = " + position + ") but '" + ch + "'");
    }

    /**
     * 判断下一个非空白字符是否为[
     */
    @Override
    public boolean checkArray() {
        char ch = this.text[++this.position];
        if (ch == '[') return true;
        if (ch == '{') return false;
        if (ch <= ' ') {
            for (;;) {
                ch = this.text[++this.position];
                if (ch > ' ') break;
            }
            if (ch == '[') return true;
            if (ch == '{') return false;
        }
        throw new JsonException("a json array text must begin with '[' (position = " + position + ") but '" + ch + "'");
    }

    /**
     * 判断下一个非空白字符是否:
     */
    @Override
    public void checkColon() {
        char ch = this.text[++this.position];
        if (ch == ':') return;
        if (ch <= ' ') {
            for (;;) {
                ch = this.text[++this.position];
                if (ch > ' ') break;
            }
            if (ch == ':') return;
        }
        throw new JsonException("expected a ':' after a key (position = " + position + ")");
    }

    @Override
    public boolean hasNext() {
        char ch = this.text[++this.position];
        if (ch == ',') return true;
        if (ch == '}' || ch == ']') return false;
        if (ch <= ' ') {
            for (;;) {
                ch = this.text[++this.position];
                if (ch > ' ') break;
            }
            if (ch == ',') return true;
            if (ch == '}' || ch == ']') return false;
        }
        this.position--;
        return true;
    }

    @Override
    protected void back(char ch) {
        this.position--;
    }

    @Override
    public final JsonDeHandle readField(final JsonDeHandle handle) {
        final char[] text0 = this.text;
        final char[] fields = handle.fields;
        int currpos = this.position;
        char quote = text0[++currpos];
        if (quote <= ' ') {
            for (;;) {
                quote = text0[++currpos];
                if (quote > ' ') break;
            }
        }
        final int size = fields.length;
        int i = 0;
        int start = currpos;
        if (quote == '"' || quote == '\'') {
            start++;
            for (;;) {
                char ch = text0[++currpos];
                if (ch == quote) break;
                if (i >= size || ch != fields[i]) i = size;
                i++;
            }
            this.position = currpos;
            if (i == size) return handle;
        } else {
            i++;
            for (;;) {
                char ch = text0[++currpos];
                if (ch == ':' || ch == '}' || ch == ']' || ch == ',' || ch <= ' ') break;
                if (i >= size || ch != fields[i]) i = size;
                i++;
            }
            this.position = currpos - 1;
            if (i == size) return handle;
        }
        final int len = currpos - start;
        JsonDeHandle result = handle.find(text0, start, len);
        if (result == null && len == 1 && text0[start] == '@') return JsonDeHandle.REFER;
        return result;
    }

    @Override
    public final char[] readValue() {
        final int eof = this.limit;
        if (this.position == eof) return null;
        final char[] text0 = this.text;
        int currpos = this.position;
        char ch = text0[++currpos];
        if (ch <= ' ') {
            for (;;) {
                ch = text0[++currpos];
                if (ch > ' ') break;
            }
        }
        if (ch == '"' || ch == '\'') {
            final char quote = ch;
            final int start = currpos + 1;
            for (;;) {
                ch = text0[++currpos];
                if (ch == '\\') {
                    this.position = currpos - 1;
                    return readEscapeValue(quote, start);
                } else if (ch == quote) {
                    break;
                }
            }
            this.position = currpos;
            char[] chs = new char[currpos - start];
            System.arraycopy(text0, start, chs, 0, chs.length);
            return chs;
        } else {
            int start = currpos;
            for (;;) {
                if (currpos == eof) break;
                ch = text0[++currpos];
                if (ch == ',' || ch == ']' || ch == '}' || ch <= ' ' || ch == ':') break;
            }
            int len = currpos - start;
            if (len < 1) {
                this.position = currpos;
                return new char[]{ch};
            }
            this.position = currpos - 1;
            if (len == 4 && text0[start] == 'n' && text0[start + 1] == 'u' && text0[start + 2] == 'l' && text0[start + 3] == 'l') return null;
            char[] rs = new char[len];
            System.arraycopy(text0, start, rs, 0, len);
            return rs;
        }
    }

    @Override
    public final long readLong() {
        final char[] text0 = this.text;
        final int eof = this.limit;
        int currpos = this.position;
        char firstchar = text0[++currpos];
        if (firstchar <= ' ') {
            for (;;) {
                firstchar = text0[++currpos];
                if (firstchar > ' ') break;
            }
        }
        if (firstchar == '"' || firstchar == '\'') firstchar = text0[++currpos];
        long value = 0;
        final boolean negative = firstchar == '-';
        if (!negative) {
            if (firstchar < '0' || firstchar > '9') throw new NumberFormatException("illegal escape(" + firstchar + ") (position = " + currpos + ")");
            value = firstchar - '0';
        }
        for (;;) {
            if (currpos == eof) break;
            char ch = text0[++currpos];
            if (ch >= '0' && ch <= '9') {
                value = (value << 3) + (value << 1) + (ch - '0');
            } else if (ch == '"' || ch == '\'') {
                continue;
            } else if (ch == ',' || ch == '}' || ch == ']' || ch <= ' ' || ch == ':') {
                break;
            } else {
                throw new NumberFormatException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "'");
            }
        }
        this.position = currpos - 1;
        return negative ? -value : value;
    }

    @Override
    public final int readInt() {
        final char[] text0 = this.text;
        final int eof = this.limit;
        int currpos = this.position;
        char firstchar = text0[++currpos];
        if (firstchar <= ' ') {
            for (;;) {
                firstchar = text0[++currpos];
                if (firstchar > ' ') break;
            }
        }
        if (firstchar == '"' || firstchar == '\'') firstchar = text0[++currpos];
        int value = 0;
        final boolean negative = firstchar == '-';
        if (!negative) {
            if (firstchar < '0' || firstchar > '9') throw new NumberFormatException("illegal escape(" + firstchar + ") (position = " + currpos + ")");
            value = firstchar - '0';
        }
        for (;;) {
            if (currpos == eof) break;
            char ch = text0[++currpos];
            if (ch >= '0' && ch <= '9') {
                value = (value << 3) + (value << 1) + (ch - '0');
            } else if (ch == '"' || ch == '\'') {
                continue;
            } else if (ch == ',' || ch == '}' || ch == ']' || ch <= ' ' || ch == ':') {
                break;
            } else {
                throw new NumberFormatException("illegal escape(" + ch + ") (position = " + currpos + ")");
            }
        }
        this.position = currpos - 1;
        return negative ? -value : value;
    }

    @Override
    public String readString() {
        final char[] text0 = this.text;
        int currpos = this.position;
        char expected = text0[++currpos];
        if (expected <= ' ') {
            for (;;) {
                expected = text0[++currpos];
                if (expected > ' ') break;
            }
        }
        if (expected != '"' && expected != '\'') {
            if (expected == 'n' && text0.length > currpos + 3) {
                if (text0[++currpos] == 'u' && text0[++currpos] == 'l' && text0[++currpos] == 'l') {
                    this.position = currpos;
                    if (text0.length > currpos + 4) {
                        char ch = text0[currpos + 1];
                        if (ch == ',' || ch <= ' ' || ch == '}' || ch == ']' || ch == ':') return null;
                    } else {
                        return null;
                    }
                }
            }
            this.position = currpos;
            throw new JsonException("expected a ':' after a key but '" + text0[position] + "' (position = " + position + ")");
        }
        final int start = ++currpos;
        for (;;) {
            char ch = text0[currpos];
            if (ch == expected) {
                break;
            } else if (ch == '\\') {
                this.position = currpos - 1;
                return new String(readEscapeValue(expected, start));
            }
            currpos++;
        }
        this.position = currpos;
        return new String(text0, start, currpos - start);
    }

    private char[] readEscapeValue(final char expected, int start) {
        CharArray array = JsonPool.pollCharArray();
        final char[] text0 = this.text;
        int pos = this.position;
        array.add(text0, start, pos + 1 - start);
        char c;
        for (;;) {
            c = text0[++pos];
            if (c == expected) {
                this.position = pos;
                char[] rs = array.toArray();
                JsonPool.offer(array);
                return rs;
            } else if (c == '\\') {
                c = text0[++pos];
                switch (c) {
                    case '"':
                    case '\'':
                    case '\\':
                    case '/':
                        array.add(c);
                        break;
                    case 'n':
                        array.add('\n');
                        break;
                    case 'r':
                        array.add('\r');
                        break;
                    case 'u':
                        array.add((char) Integer.parseInt(new String(new char[]{text0[++pos], text0[++pos], text0[++pos], text0[++pos]}), 16));
                        break;
                    case 't':
                        array.add('\t');
                        break;
                    case 'b':
                        array.add('\b');
                        break;
                    case 'f':
                        array.add('\f');
                        break;
                    default:
                        this.position = pos;
                        throw new JsonException("illegal escape(" + c + ") (position = " + this.position + ")");
                }
            } else {
                array.add(c);
            }
        }
    }
}
